package list

import (
	"gitee.com/Billcoding/gotypes"
	"sync"
)

//Define ArrayList struct
type ArrayList struct {
	//A sync locker
	mutex sync.Mutex
	//elements
	elements []*gotypes.Element
}

//create new empty List
func NewArrayList() *ArrayList {
	elements := make([]*gotypes.Element, 0)
	return &ArrayList{
		elements: elements,
		mutex:    sync.Mutex{},
	}
}

//clear list
func (arrayList *ArrayList) Clear() {
	arrayList.elements = make([]*gotypes.Element, 0)
}

//return list size
func (arrayList *ArrayList) Size() int {
	return len(arrayList.elements)
}

//add element
func (arrayList *ArrayList) Add(e gotypes.Element) *ArrayList {
	arrayList.mutex.Lock()
	defer arrayList.mutex.Unlock()
	arrayList.elements = append(arrayList.elements, &e)
	return arrayList
}

//get index element
func (arrayList *ArrayList) Get(index int) *gotypes.Element {
	var e *gotypes.Element
	size := arrayList.Size()
	if size > 0 && index >= 0 && index <= size-1 {
		e = arrayList.elements[index]
	}
	return e
}

//remove index element
func (arrayList *ArrayList) Remove(index int) *gotypes.Element {
	var e *gotypes.Element
	arrayList.mutex.Lock()
	defer arrayList.mutex.Unlock()
	size := arrayList.Size()
	if size > 0 && index >= 0 && index <= size-1 {
		e = arrayList.elements[index]
		es := make([]*gotypes.Element, 0)
		if index > 0 {
			//copy left
			es = append(es, arrayList.elements[:index]...)
		}
		if index < size-1 {
			//copy right
			es = append(es, arrayList.elements[index+1:]...)
		}
		arrayList.elements = es
	}
	return e
}

//foreach function
func (arrayList *ArrayList) ForEach(fn func(e *gotypes.Element)) {
	for _, e := range arrayList.elements {
		fn(e)
	}
}

//map function
func (arrayList *ArrayList) Map(mapFn func(e *gotypes.Element) gotypes.Element) *ArrayList {
	return arrayList.fn(func(e *gotypes.Element) bool {
		return true
	}, mapFn)
}

//filter function
func (arrayList *ArrayList) Filter(filterFn func(e *gotypes.Element) bool) *ArrayList {
	return arrayList.fn(filterFn, func(e *gotypes.Element) gotypes.Element {
		return *e
	})
}

//reduce function
func (arrayList *ArrayList) Reduce(val gotypes.Element, reduceFn func(val gotypes.Element, e *gotypes.Element) gotypes.Element) gotypes.Element {
	firstPtr := &val
	arrayList.ForEach(func(e *gotypes.Element) {
		*firstPtr = reduceFn(*firstPtr, e)
	})
	return *firstPtr
}

//matchAny function
func (arrayList *ArrayList) MatchAny(matchAnyFn func(e *gotypes.Element) bool) bool {
	for _, e := range arrayList.elements {
		if matchAnyFn(e) {
			return true
		}
	}
	return false
}

//matchNone function
func (arrayList *ArrayList) MatchNone(matchNoneFn func(e *gotypes.Element) bool) bool {
	for _, e := range arrayList.elements {
		if matchNoneFn(e) {
			continue
		}
		return true
	}
	return false
}

//matchAll function
func (arrayList *ArrayList) MatchAll(matchAllFn func(e *gotypes.Element) bool) bool {
	count := 0
	for _, e := range arrayList.elements {
		if matchAllFn(e) {
			count++
		}
	}
	return count == arrayList.Size()
}

func (arrayList *ArrayList) fn(filterFn func(e *gotypes.Element) bool, mapFn func(e *gotypes.Element) gotypes.Element) *ArrayList {
	newArrayList := NewArrayList()
	for _, e := range arrayList.elements {
		val := e
		if filterFn(val) {
			newArrayList.Add(mapFn(val))
		}
	}
	return newArrayList
}
